OS: Windows 10
Editor: Visual Studio Code
Rust version: 1.63.0
在Rust中,定義函式都已fn
作為開頭,下面的範例是一個不回傳任何值與不傳入任何參數的函式。
fn hello_function() {
println!("Hello, function!");
}
參數列的話跟很多語言一樣,都是放在函式名稱後面的括號裡:
fn float_say_hello_to_integar(x: f32, y: i32) {
println!("Hello, {}! This is {}", y, x);
}
但如果有相同型別的參數的話,Rust不能類似像Golang這樣寫:
fn add_print(x, y: i32) {
println!("Add: {}", (x + y));
}
/* 這是golang合法的寫法 */
func AddPrint(x, y int) {
fmt.Println("Add: ", (x + y))
}
當然不可少的,函式回傳的部分:
fn give_me_five() -> i32 {
return 5;
}
如果還記得前幾天講到的,回傳多個值時,除了想像C
家族的語言那樣,把要回傳的值包成struct
,把值包進struct
裡回傳回來,或是利用tuple
!
fn give_me_555() -> (i32, i32, i32) {
return (5, 5, 5);
}
fn main() {
let (a, b, c) = give_me_555(); // 解構
println!("a is {}", a);
println!("b is {}", b);
println!("c is {}", c);
}
再來是一個有趣的點,回傳不一定需要有return
,所以下面的寫法是合法的:
fn give_me_num(x: i32) -> i32 {
x
}
fn main() {
let x = give_me_num(47);
println!("x is {}", x);
}
至於為什麼不寫return
也可以,下面會試著解說。
首先先來澄清甚麼是陳述句(statement) 與 表達句(expression)
這是一個陳述句
let num = 6 + 7;
一個陳述句當中可以包含表達句,以上面的範例6 + 7
就是表達句,以我有接觸過的語言來說,雖說一樣有陳述句與表達句的概念,但沒有像Rust那樣需要這麼注意這個概念。
PS: Rust是門基於表達式 (expression-based) 的語言。
從上面的描述的可以知道,表達式會給出一個值,再給個範例
let ok = false;
在這裡,false
就是表達句。
呼叫函式是表達式,呼叫巨集(macro)是表達式,在scope{}當中進行的也可以是做一個表達式:
// expression
let x = {
let y = 6;
y + 1
};
// x is 7
結論,表達句會計算出一個值,並回傳回來,陳述句則不會,陳述句是對某件事的處理的指令。
所以這就是為什麼可以直接這樣寫:
// 上面的例子
fn give_me_num(x: i32) -> i32 {
x
}
如同上面所說的,他計算了一個值x
然後把x
回傳回去。
由於這個特性的關係,讓我們可以有更簡潔的寫法:
let x = -99;
let sign = if x < 0 { -1 } else { 1 };
而且重要的一點,不用去擔心宣告的變數是不是空值(null
/nil
)
PS: Rust中並沒有null
這個概念
// 不安全,可能會有空字串的情況
let num = 5;
let mut name = "";
if num > 3 {
name = "Tom";
} else {
name = "Jerry";
}
println!("{}", name);
// 相對安全
let num = 5;
let name = if num > 3 {
"Tom"
} else {
"Jerry"
};
println!("{}", name);
main
function我們遇到的第一個函式,應該是main()
,許多情況下,main()
確實不用回傳任何東西,但有件事引起了我的好奇:
int
作為error code
。然後確實有這個東西,從官方教材中找到了。
以下是範例,由參數列輸入,嘗試將輸入轉換成數字。
use std::env;
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let args: Vec<String> = env::args().collect();
let num_str = &args[1];
let num = match num_str.parse::<i32>() {
Ok(num) => num,
Err(e) => return Err(e),
};
println!("Num is {}", num);
Ok(())
}
Wow! 突然多了好多前面沒看過的東西,先一個一個把不清楚的地方標出來:
use std::env;
use std::num::ParseIntError;
// Q1: `Result`看起來是回傳一個泛型?
// Q2: `()` 是什麼型別?
fn main() -> Result<(), ParseIntError> {
// Q3: `Vec<String>`是Rust的動態陣列嗎?
// Q4: `collect()`是做什麼用的?
let args: Vec<String> = env::args().collect();
// Q5: 為什麼要用 &?
let num_str = &args[1];
// Q6: `parse::<i32>`是Rust泛型函式的用法嗎?
let num = match num_str.parse::<i32>() {
Ok(num) => num,
Err(e) => return Err(e),
};
println!("Num is {}", num);
Ok(())
}
接下來一個一個開始去找答案,當然可能會發現找A的時候,會出現B、C、D等新名詞,甚至是完全理解錯誤,但就先稍微了解一下,等之後再碰到,再來填坑 (挖坑~ 挖坑中~)。
Result
這是一個generics enum的型別,實際定義長這樣:
enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error.
Ok(T),
Err(E),
}
這屬於Rust錯誤處理的方式,其他語言中都有像是null
、nil
來表示空值,但Rust中並沒有,而是用Option<T>
回傳Some(value)
表示某值或是None
表示空值,Result<T, E>
則是會回傳錯誤,如果錯誤發生的話。
()
Type這個可以視為C語言家族中的void
。
以上面的範例來看的話,就會是main
function回傳一個Result<(), ParseIntError>
,如果錯誤的話,就回傳錯誤(Line: 19),正常就表示不想要任何回傳值(Line: 22)。
Vec<>
是Rust的動態陣列嗎?確實沒錯!以範例來看就是Rust中的動態陣列(Growable array),類似C++的std::vector
。
參考文件式這份。
collect()
是做什麼用的?這個問題要看整個表達式(expression),env::args().collect()
,前面env::args()
挺好理解的,就是要取得程式開啟時傳入的參數列,但查詢文件可以得知,env::args()
回傳的是迭代器(iterator),後面的collect()
則是把迭代器轉換成集合(collection)。
文件請見這裡,進去之後還要搜尋一下collect
,就會找到了。
&
?問這個問題其實還有另外一個點,雖然八九不離十跟參考(Reference)之類的有關係,但是為什麼一定要拿它的參考,於是我把&
拿掉看看:
let args: Vec<String> = env::args().collect();
let num_str = args[1]; // 把`&`拿掉看看
compiler回這個錯誤給我:
error[E0507]: cannot move out of index of `Vec<String>`
--> src\main.rs:6:19
|
6 | let num_str = args[1];
| ^^^^^^^
| |
| move occurs because value has type `String`, which does not implement the `Copy` trait
| help: consider borrowing here: `&args[1]`
For more information about this error, try `rustc --explain E0507`.
查了一下,看起來跟是String
這個型別不給複製,因為沒有相關對於Copy
的實作。
parse::<i32>
是Rust泛型函式的用法嗎?這個確實為Rust泛型函式的用法,參考的地方在這裡,但目前只想先知道式泛型就好了,等到介紹到他之後,再去進一步了解它。
本篇的後面,稍微找了一下關於我對main
function的疑惑,當然是很草率的了解一下,尤其是關於參考與複製的部分,由於碰過C和C++,有把這兩種語言的特性帶進去思考,但跟Rust比起來有很多地方都不一樣......